#if DIRECT3D11
using SharpDX;
using SharpDX.Direct3D11;
#else
using Silk.NET.OpenGLES;
#endif
using System.Runtime.InteropServices;

namespace Engine.Graphics {
    public class IndexBuffer : GraphicsResource {
#if DIRECT3D11
        public SharpDX.Direct3D11.Buffer m_buffer;
#else
        public int m_buffer;
#endif

        public string DebugName {
            get {
#if DIRECT3D11
                return m_buffer.DebugName;
#else
                return string.Empty;
#endif
            }
            // ReSharper disable ValueParameterNotUsed
            set
            // ReSharper restore ValueParameterNotUsed
            {
#if DIRECT3D11
                m_buffer.DebugName = value;
#endif
            }
        }

        public IndexFormat IndexFormat { get; set; }

        public int IndicesCount { get; set; }

        public object Tag { get; set; }

        public IndexBuffer(IndexFormat indexFormat, int indicesCount) {
            InitializeIndexBuffer(indexFormat, indicesCount);
            AllocateBuffer();
        }

        public override void Dispose() {
            base.Dispose();
            DeleteBuffer();
        }

        public void SetData<T>(T[] source, int sourceStartIndex, int sourceCount, int targetStartIndex = 0) where T : struct {
            VerifyParametersSetData(source, sourceStartIndex, sourceCount, targetStartIndex);
            GCHandle gCHandle = GCHandle.Alloc(source, GCHandleType.Pinned);
            try {
                int num = Utilities.SizeOf<T>();
                int size = IndexFormat.GetSize();
#if DIRECT3D11
                DataBox dataBox = new(gCHandle.AddrOfPinnedObject() + sourceStartIndex * num, 1, 0);
                ResourceRegion resourceRegion = new(targetStartIndex * size, 0, 0, targetStartIndex * size + sourceCount * num, 1, 1);
                DXWrapper.Context.UpdateSubresource(dataBox, m_buffer, 0, resourceRegion);
#else
                unsafe {
                    GLWrapper.BindBuffer(BufferTargetARB.ElementArrayBuffer, m_buffer);
                    GLWrapper.GL.BufferSubData(
                        BufferTargetARB.ElementArrayBuffer,
                        new IntPtr(targetStartIndex * size),
                        new UIntPtr((uint)(num * sourceCount)),
                        (gCHandle.AddrOfPinnedObject() + sourceStartIndex * num).ToPointer()
                    );
                }
#endif
            }
            finally {
                gCHandle.Free();
            }
        }

        public override void HandleDeviceLost() {
            DeleteBuffer();
        }

        public override void HandleDeviceReset() {
            AllocateBuffer();
        }

        public void AllocateBuffer() {
#if DIRECT3D11
            m_buffer = new SharpDX.Direct3D11.Buffer(
                DXWrapper.Device,
                IndexFormat.GetSize() * IndicesCount,
                ResourceUsage.Default,
                BindFlags.IndexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None,
                0
            );
#else
            unsafe {
                GLWrapper.GL.GenBuffers(1, out uint buffer);
                m_buffer = (int)buffer;
                GLWrapper.BindBuffer(BufferTargetARB.ElementArrayBuffer, m_buffer);
                GLWrapper.GL.BufferData(
                    BufferTargetARB.ElementArrayBuffer,
                    new UIntPtr((uint)(IndexFormat.GetSize() * IndicesCount)),
                    null,
                    BufferUsageARB.StaticDraw
                );
            }
#endif
        }

        public void DeleteBuffer() {
#if DIRECT3D11
            Utilities.Dispose(ref m_buffer);
#else
            if (m_buffer != 0) {
                GLWrapper.DeleteBuffer(BufferTargetARB.ElementArrayBuffer, m_buffer);
                m_buffer = 0;
            }
#endif
        }

        public override int GetGpuMemoryUsage() => IndicesCount * IndexFormat.GetSize();

        void InitializeIndexBuffer(IndexFormat indexFormat, int indicesCount) {
            if (indicesCount <= 0) {
                throw new ArgumentException("Indices count must be greater than 0.");
            }
            IndexFormat = indexFormat;
            IndicesCount = indicesCount;
        }

        void VerifyParametersSetData<T>(T[] source, int sourceStartIndex, int sourceCount, int targetStartIndex = 0) where T : struct {
            VerifyNotDisposed();
            int num = Utilities.SizeOf<T>();
            int size = IndexFormat.GetSize();
            ArgumentNullException.ThrowIfNull(source);
            if (sourceStartIndex < 0
                || sourceCount < 0
                || sourceStartIndex + sourceCount > source.Length) {
                throw new ArgumentException("Range is out of source bounds.");
            }
            if (targetStartIndex < 0
                || targetStartIndex * size + sourceCount * num > IndicesCount * size) {
                throw new ArgumentException("Range is out of target bounds.");
            }
        }
    }
}